﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Threading;

namespace Bildverarbeitung
{
    public class KlomannMarcelFilter : IFilter
    {
        private unsafe class Param : IWorkParameter
        {
            public int yStart;
            public int yEnd;
            public int Stride;
            public int Width;
            public byte* StartPtr;
            public byte*[] SourcePtrs;
        }

        private const int BYTES_PER_PIXEL = 3;

        private unsafe void WorkMethod(IWorkParameter param)
        {
            Param p = param as Param;
            byte*[] pointers = p.SourcePtrs;
            byte* start = p.StartPtr;
            int stride = p.Stride;

            for (int y = p.yStart; y <= p.yEnd; y++)
            {
                for (int x = 0; x < p.Width * BYTES_PER_PIXEL; x++)
                {
                    int avgBelow = 0;
                    int belowCount = 0;
                    int avgAbove = 0;
                    int aboveCount = 0;
                    int offset = y * stride + x;

                    // Einfachen Durchschnitt berechnen
                    int avg = 0;
                    for (int i = 0; i < pointers.Length; i++)
                    {
                        avg += (int)*(pointers[i] + offset);
                    }
                    avg /= pointers.Length;

                    // Neuen Durchschnitt für alle Werte unterhalb und oberhalb des Durchschnitts getrennt berechnen.
                    for (int i = 0; i < pointers.Length; i++)
                    {
                        int value = (int)*(pointers[i] + offset);
                        if (value <= avg)
                        {
                            avgBelow += value;
                            belowCount++;
                        }
                        else
                        {
                            avgAbove += value;
                            aboveCount++;
                        }
                    }
                    // Es wird der Durchschnittswert genommen, der aus mehr Werten berechnet wurde.
                    int newAvg = (belowCount > aboveCount) ?
                                 (avgBelow / belowCount) :
                                 (avgAbove / aboveCount);
                    *(start + offset) = (byte)newAvg;
                }
            }

        }

        public Bitmap GetSummerizedDifferencefromAverage(Bitmap[] sources)
        {
            if (sources == null || sources.Length == 0)
            {
                throw new ArgumentNullException("Es muss mindestens 1 Bild übergeben werden.");
            }

            int width = sources[0].Width;
            int height = sources[0].Height;

            Bitmap result = new Bitmap(width, height);
            unsafe
            {
                // Pointer besorgen
                BitmapData resultData = result.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
                byte* start = (byte*)resultData.Scan0.ToPointer();

                byte*[] pointers = new byte*[sources.Length];
                BitmapData[] bitmapData = new BitmapData[sources.Length];
                for (int i = 0; i < pointers.Length; i++)
                {
                    Bitmap image = sources[i];
                    BitmapData data = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
                    bitmapData[i] = data;
                    pointers[i] = (byte*)data.Scan0.ToPointer();
                }

                // Arbeit partitionieren für die Parallelisierung
                object locker = new object();
                Task task = new Task(WorkMethod, locker);
                for (int y = 0; y < height; y++)
                {
                    Param p = new Param()
                    {
                        yStart = y,
                        yEnd = y,
                        Stride = resultData.Stride,
                        Width = width,
                        StartPtr = start,
                        SourcePtrs = pointers
                    };
                    task.AddWork(p);
                }

                // Arbeit an den Threadpool schicken und auf Beendigung der Arbeit warten
                lock (locker)
                {
                    WorkerThreadPool.AddTask(task);
                    Monitor.Wait(locker);
                }

                // Alles wieder freigeben
                for (int i = 0; i < pointers.Length; i++)
                {
                    sources[i].UnlockBits(bitmapData[i]);
                }
                result.UnlockBits(resultData);
            }

            return result;
        }


        public string GetName()
        {
            return "KlomannMarcel";
        }
    }
	
	public interface IWorkParameter { }
	
	/// <summary>
    /// Callback thats called when the work is done.
    /// </summary>
    public delegate void WorkCallback();

    /// <summary>
    /// The method that is called to complete the task.
    /// </summary>
    /// <param name="param">The parameter object that contains all task-specific parameters for the method</param>
    public delegate void TaskMethod(IWorkParameter param);

    /// <summary>
    /// Describes a task containing one or multiple jobs to be completed by a thread pool.
    /// </summary>
    public class Task
    {
        #region Fields

        internal long JobsDone;
        private long jobCount;
        private object locker;
        private WorkCallback callback;
        private TaskMethod taskMethod;
        private Queue<IWorkParameter> work;

        #endregion

        #region Properties

        /// <summary>
        /// The number of jobs for this task
        /// </summary>
        public long JobCount
        {
            get { return jobCount; }
        }

        /// <summary>
        /// Gets the object used to wait for the completion of the work.
        /// When the work is done, the last worker thread notifies the client using this object.
        /// Ignored if the value is null.
        /// </summary>
        public object Locker 
        {
            get
            {
                return locker;
            }
        }

        /// <summary>
        /// Gets the callback for this job.
        /// Is called when the task is done. 
        /// Ignored if the value is null.
        /// </summary>
        public WorkCallback Callback 
        {
            get
            {
                return callback;
            }
        }

        /// <summary>
        /// Gets the method that is called to complete the task.
        /// </summary>
        public TaskMethod TaskMethod
        {
            get { return taskMethod; }
        }

        /// <summary>
        /// The number of work left for this task
        /// </summary>
        public int WorkCount
        {
            get { return work.Count; }
        }

        #endregion

        #region Initialization

        /// <summary>
        /// Creates a new task.
        /// </summary>
        /// <param name="taskMethod">The method that is called to complete the task</param>
        public Task(TaskMethod taskMethod)
            : this(taskMethod, null, null)
        {
            //if (taskMethod == null)
            //    throw new ArgumentNullException("taskMethod");
            //this.taskMethod = taskMethod;
            //this.work = new Queue<IWorkParam>();
            //callback = null;
            //locker = null;
        }

        /// <summary>
        /// Creates a new task.
        /// </summary>
        /// <param name="taskMethod">The method that is called to complete the task</param>
        /// <param name="locker">The object used to wait for the completion of the task</param>
        public Task(TaskMethod taskMethod, object locker)
            : this(taskMethod, null, locker)
        {
            //this.locker = locker;
        }

        /// <summary>
        /// Creates a new task.
        /// </summary>
        /// <param name="taskMethod">The method that is called to complete the task</param>
        /// <param name="callback">The method that is called when the task is completed</param>
        public Task(TaskMethod taskMethod, WorkCallback callback)
            : this(taskMethod, callback, null)
        {
            //this.callback = callback;
        }

        /// <summary>
        /// Creates a new task.
        /// </summary>
        /// <param name="taskMethod">The method that is called to complete the task</param>
        /// <param name="callback">The method that is called when the task is completed</param>
        /// <param name="locker">The object used to wait for the completion of the task</param>
        public Task(TaskMethod taskMethod, WorkCallback callback, object locker)
        {
            if (taskMethod == null)
                throw new ArgumentNullException("taskMethod");
            this.work = new Queue<IWorkParameter>();
            this.taskMethod = taskMethod;
            this.callback = callback;
            this.locker = locker;
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Adds a job to the task.
        /// Shouldn't be called when the task is already in progress.
        /// </summary>
        /// <param name="workParameter">The parameters for the task</param>
        public void AddWork(IWorkParameter workParameter)
        {
            work.Enqueue(workParameter);
            jobCount++;
        }

        /// <summary>
        /// Gets a work item.
        /// </summary>
        /// <returns>The work parameters or null if the task is completed</returns>
        public IWorkParameter Get()
        {
            if (work.Count == 0)
                return null;
            return work.Dequeue();
        }

        #endregion
    }
	
	internal class Queue<T>
    {
        private const int DEFAULT_SIZE = 1024;
        private T[] items;
        private int front, rear;

        public int Count
        {
            get { return rear - front; }
        }

        public bool IsEmpty
        {
            get { return rear == front; }
        }

        public Queue()
        {
            this.Initialize();
        }

        public void Enqueue(T item)
        {
            if (rear == this.items.Length)
            {
                this.Resize();
            }
            this.items[rear++] = item;
        }

        public T Dequeue()
        {
            if (IsEmpty)
            {
                throw new InvalidOperationException("Queue ist leer.");
            }
            T item = this.items[front];
            this.items[front++] = default(T);
            return item;
        }

        public void Clear()
        {
            this.Initialize();
        }

        private void Initialize()
        {
            this.items = new T[DEFAULT_SIZE];
            this.rear = 0;
            this.front = 0;
        }

        private void Resize()
        {
            T[] newItems = new T[this.items.Length * 2];
            for (int i = 0; i < this.items.Length; i++)
            {
                newItems[i] = this.items[i];
            }
            this.items = newItems;
        }
    }

    /// <summary>
    /// A class that contains several worker thread.
    /// The worker threads will complete enqueued tasks.
    /// </summary>
    public static class WorkerThreadPool
    {
        #region Fields

        private static Thread[] workerThreads;
        private static Queue<Task> workQueue;

        #endregion

        #region Properties

        /// <summary>
        /// Gets the number of active threads in the pool
        /// </summary>
        public static int ThreadCount
        {
            get
            {
                if (workerThreads == null)
                    return 0;
                return workerThreads.Length;
            }
        }

        #endregion

        #region Initialization

        /// <summary>
        /// Initializes the thread pool
        /// </summary>
        static WorkerThreadPool()
        {
            int threads = 4;
            try
            {
                threads = Int32.Parse(System.Environment.GetEnvironmentVariable("NUMBER_OF_PROCESSORS"));
            }
            catch (Exception e) { }

            workerThreads = new Thread[threads];
            for (int i = 0; i < threads; i++)
            {
                workerThreads[i] = new Thread(new ThreadStart(Run));
                workerThreads[i].IsBackground = true;
                workerThreads[i].Start();
            }
            workQueue = new Queue<Task>();
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Adds a task to the work queue
        /// </summary>
        /// <param name="task"></param>
        public static void AddTask(Task task)
        {
            lock (workQueue)
            {
                workQueue.Enqueue(task);
                Monitor.PulseAll(workQueue);
            }
        }

        /// <summary>
        /// Removes all tasks from the work queue
        /// </summary>
        public static void Clear()
        {
            lock (workQueue)
            {
                workQueue.Clear();
            }
        }

        #endregion

        #region Run

        private static void Run()
        {
            Task task;
            IWorkParameter param;

            try
            {
                while (true)
                {
                    lock (workQueue)
                    {
                        while (workQueue.Count == 0) Monitor.Wait(workQueue);
                        task = workQueue.Dequeue();
                        param = task.Get();
                        if (task.WorkCount > 0)
                        {
                            workQueue.Enqueue(task);
                        }
                    }

                    task.TaskMethod(param);

                    Interlocked.Increment(ref task.JobsDone);
                    if (Interlocked.Read(ref task.JobsDone) == task.JobCount)
                    {
                        if (task.Callback != null)
                        {
                            task.Callback();
                        }
                        if (task.Locker != null)
                        {
                            lock (task.Locker)
                            {
                                Monitor.PulseAll(task.Locker);
                            }
                        }
                    }
                }
            }
            catch (ThreadAbortException)
            { }
        }

        #endregion
    }
}
